package com.gitee.wsl.text.ahocorasick


import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class AhoCorasickTest {

//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `root node can't be moved by a null character child`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        // First, create the "a" node with the child "c" ("b" cant be used because the root node has a base offset of
//        // one). Then, add the null character to the "a" node, which will cause a conflict between the "a" node with the
//        // root node as the null character is attempted to be placed into the same location as the root node. Normally,
//        // the node with the least amount of children should have its children moved, but this should NOT be the case if
//        // a node that would need to be moved is the root node.
//        ahoCorasick.buildWith("ac", "a\u0000")
//
//        val results = ahoCorasick.parse("aca\u0000")
//        assertThat(results).containsExactly("ac", "a\u0000")
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `null character can be used without issue`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("\u0000", "\u0000cat\u0000")
//
//        val results = ahoCorasick.parse("cat\u0000 \u0000cat\u0000")
//        assertThat(results).containsExactly("\u0000", "\u0000", "\u0000cat\u0000", "\u0000")
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `can build with no added strings`(ahoCorasick: StringAhoCorasickWrapper) {
//        assertDoesNotThrow { ahoCorasick.build() }
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `can search words with no added strings`(ahoCorasick: StringAhoCorasickWrapper) {
//        ahoCorasick.build()
//        assertThat(ahoCorasick.parse("nothing expected to be found")).isEmpty()
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `throws error when build is called twice`(ahoCorasick: StringAhoCorasickWrapper) {
//        ahoCorasick.build()
//        assertThrows<IllegalStateException> { ahoCorasick.build() }
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `throws error when adding empty string`(ahoCorasick: StringAhoCorasickWrapper) {
//        assertThrows<IllegalArgumentException> { ahoCorasick.buildWith("") }
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `throws error when attempting to find strings before building`(ahoCorasick: StringAhoCorasickWrapper) {
//        assertThrows<IllegalStateException> { ahoCorasick.parse("Should throw") }
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `throws error when objects are added after built has been called`(
//        ahoCorasick: StringAhoCorasickWrapper,
//    ) {
//        ahoCorasick.build()
//        assertThrows<IllegalStateException> { ahoCorasick.addString("value") }
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `detects multiple words ending at same location`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("bobcat", "cat", "at")
//
//        val results = ahoCorasick.parse("I have a bobcat")
//        assertThat(results).containsExactly("bobcat", "cat", "at")
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `words are found in ascending order by ending position, and descending size from same position`(
//        ahoCorasick: StringAhoCorasickWrapper,
//    ) {
//        ahoCorasick.buildWith("cat", "at", "catapult", "tap", "a", "t")
//
//        val results = ahoCorasick.parse("catapult")
//        assertThat(results).containsExactly("a", "cat", "at", "t", "a", "tap", "catapult", "t").inOrder()
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `detects partially overlapping words`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("baby", "byte")
//
//        val results = ahoCorasick.parse("babyte")
//        assertThat(results).containsExactly("baby", "byte")
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `handles non ASCII characters`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("❤️", "✨", "🐱")
//
//        val results = ahoCorasick.parse("Hows ❤️ it ✨ going 🐱 bestie")
//        assertThat(results).containsExactly("❤️", "✨", "🐱")
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `is case sensitive by default`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("cAt", "CaT")
//
//        val results = ahoCorasick.parse("CAT CaT CAt Cat cAT caT cAt cat")
//        assertThat(results).containsExactly("cAt", "CaT")
//    }
//
//    @ParameterizedTest
//    @MethodSource("caseInsensitiveAhoCorasickStructures")
//    internal fun `supports case insensitive mode`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("cat")
//
//        val results = ahoCorasick.parse("CAT CaT CAt Cat cAT caT cAt cat").map {
//            // Generic version will match original casing, optimized will match casing in input
//            it.lowercase(Locale.ROOT)
//        }
//        assertThat(results).containsExactly("cat", "cat", "cat", "cat", "cat", "cat", "cat", "cat")
//    }
//
//    @ParameterizedTest
//    @MethodSource("wholeWordAhoCorasickStructures")
//    internal fun `supports word boundary mode`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("Expected", "Double Expected", "Exp")
//
//        val results = ahoCorasick.wrappedAhoCorasick.parse(
//            "Double Expected\tnotExpected notDouble\rExpected Expected\nExpectedNot Exp",
//        ).map { it.start }.toList()
//
//        assertThat(results).containsExactly(0, 7, 38, 47, 68)
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `results supply correct indexes`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("cat", "at", "a")
//
//        val results = ahoCorasick.wrappedAhoCorasick.parse("cat at a house").toList()
//        assertThat(results).containsExactly(
//            AhoCorasickResult(0, 3, "cat"),
//            AhoCorasickResult(1, 3, "at"),
//            AhoCorasickResult(1, 2, "a"),
//            AhoCorasickResult(4, 6, "at"),
//            AhoCorasickResult(4, 5, "a"),
//            AhoCorasickResult(7, 8, "a"),
//        )
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `node count is calculated corrected`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.buildWith("fire hydrant", "fire truck")
//
//        // Start with one for the root node
//        val expected = 1 + "fire hydrant".length + "truck".length
//
//        assertThat(ahoCorasick.wrappedAhoCorasick.nodes).isEqualTo(expected)
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `isBuilt correctly reflects state`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        assertThat(ahoCorasick.wrappedAhoCorasick.isBuilt).isFalse()
//        ahoCorasick.buildWith("irrelevant value")
//        assertThat(ahoCorasick.wrappedAhoCorasick.isBuilt).isTrue()
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `contains determines if a key exists at all build stages`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.addString("iExist")
//        assertThat(ahoCorasick.wrappedAhoCorasick.contains("iExist")).isTrue()
//        assertThat(ahoCorasick.wrappedAhoCorasick.contains("iDontExist")).isFalse()
//
//        ahoCorasick.build()
//        assertThat(ahoCorasick.wrappedAhoCorasick.contains("iExist")).isTrue()
//        assertThat(ahoCorasick.wrappedAhoCorasick.contains("iDontExist")).isFalse()
//    }
//
//    @ParameterizedTest
//    @MethodSource("emptyAhoCorasickStructures")
//    internal fun `offset greater than length of double array is handled while building`(
//        ahoCorasick: StringAhoCorasickWrapper,
//    ) {
//        // The below setup would cause an index out of bounds exception if one did not safely get the value at an index.
//        ahoCorasick.addString("cab")
//        ahoCorasick.addString("aa")
//
//        assertDoesNotThrow { ahoCorasick.build() }
//    }
//
//    @ParameterizedTest
//    @MethodSource("caseInsensitiveAhoCorasickStructures")
//    internal fun `maintains string length when handling case insensitivity`(ahoCorasick: StringAhoCorasickWrapper) {
//
//        ahoCorasick.addString("0")
//        ahoCorasick.build()
//
//        val result = ahoCorasick.wrappedAhoCorasick.parse("İ0").toList().first()
//
//        // If handled in a way that didn't maintain string lengths, İ would map to two characters, giving invalid bounds
//        // for the '0' character match.
//        assertThat(result).isEqualTo(AhoCorasickResult(1, 2, "0"))
//    }

    @Test
    internal fun `generic structure replaces value on duplicate keys`() {

        val genericAhoCorasick = AhoCorasick<Int>().apply {
            add("lucky", 0)
            add("lucky", 7)
            build()
        }

        assertEquals(genericAhoCorasick.valueOf("lucky"),7)
        val results = runBlocking { genericAhoCorasick.parseAsFlow("lucky").toList()}
        assertEquals(results[0].value,7)
    }

    @Test
    internal fun `generic structure valueOf works at various build stages`() {

        val genericAhoCorasick = AhoCorasick<Int>().apply {
            add("existingKey", 7)
        }

        assertEquals(genericAhoCorasick.valueOf("existingKey"),7)
        assertEquals(genericAhoCorasick.valueOf("nonExistingKey"),null)

        genericAhoCorasick.build()

        assertEquals(genericAhoCorasick.valueOf("existingKey"),7)
        assertEquals(genericAhoCorasick.valueOf("nonExistingKey"),null)
    }

    @Test
    fun `generic structure supports values differing from keys`() {

        val genericAhoCorasick = AhoCorasick<String>().apply {
            add("np", "no problem")
            add("ty", "thank you")
            build()
        }

        //val results = genericAhoCorasick.parse("It was np, ty though.").map { it.value }.toList()
        val results = runBlocking { genericAhoCorasick.parseAsFlow("It was np, ty though.").toList().map { it.value }}
        assertTrue(results.containsAll(listOf("no problem", "thank you")))
    }

    @Test
    fun `generic structure supports replacing values`(): Unit = with(AhoCorasick<String>()) {

        add("key", "original")

        // Replace existing value
        assertTrue(replace("key", "newValue"))
        assertEquals(valueOf("key"),"newValue")

        // Replacement fails when value does not exist
        assertFalse(replace("newKey", "newValue"))
        assertFalse(contains("newKey"))

        // Replacement with insertion if value does not exist
        assertFalse(replace("newKey", "newKeyValue", true))
        assertTrue(contains("newKey"))

        build()

        // Replacement fails after build has been called
        assertFailsWith<IllegalStateException> { replace("anotherKey", "newValue", true) }
        // Insertion via replacement fails after build has been called
        assertFailsWith<IllegalStateException> { replace("newKey", "differentValue") }

        //val results = parse("key newKey").map { it.value }.toList()
        val results = runBlocking { parseAsFlow("key newKey").toList().map { it.value }}
        assertTrue(results.containsAll(listOf("newValue", "newKeyValue")))
    }

    @Test
    fun `string structure returns same casing as input when case insensitive`() {

        val ahoCorasick = StringAhoCorasick(setOf(AhoCorasickOption.CASE_INSENSITIVE)).apply {
            add("np")
            add("ty")
            add("PARTY")
            build()
        }

        //val results = ahoCorasick.parse("Thanks for inviting me to the pARTy. np.").map { it.value }.toList()
        val results = runBlocking { ahoCorasick.parseAsFlow("key newKey").toList().map { it.value }}
        assertTrue(results.containsAll(listOf("pARTy", "Ty", "np")))
    }

    companion object {

//        @JvmStatic
//        fun emptyAhoCorasickStructures(): Stream<Arguments> = Stream.of(
//            arguments(StringAhoCorasickWrapper(AhoCorasick())),
//            arguments(StringAhoCorasickWrapper(StringAhoCorasick())),
//        )
//
//        @JvmStatic
//        fun caseInsensitiveAhoCorasickStructures(): Stream<Arguments> = Stream.of(
//            arguments(StringAhoCorasickWrapper(AhoCorasick(setOf(AhoCorasickOption.CASE_INSENSITIVE)))),
//            arguments(StringAhoCorasickWrapper(StringAhoCorasick(setOf(AhoCorasickOption.CASE_INSENSITIVE)))),
//        )
//
//        @JvmStatic
//        fun wholeWordAhoCorasickStructures(): Stream<Arguments> = Stream.of(
//            arguments(StringAhoCorasickWrapper(AhoCorasick(setOf(AhoCorasickOption.WHOLE_WORDS_ONLY)))),
//            arguments(StringAhoCorasickWrapper(StringAhoCorasick(setOf(AhoCorasickOption.WHOLE_WORDS_ONLY)))),
//        )
    }

    /**
     * Wraps around a generic form of the Aho-Corasick structure, as well as an optimized version for strings.
     *
     * This wrapper is necessary to conform the two structures to a common interface. The generic version allows for any
     * value to be associated with a string key, while in the optimized string version the key is also the value, making
     * its add function require only one argument. This wrapper's [addString] function only takes a single string, and
     * simply passes that value as both the key and value for the generic version of the algorithm.
     */
    internal class StringAhoCorasickWrapper {

        val wrappedAhoCorasick: AhoCorasickBase<String>
        val addString: (string: String) -> Unit

        constructor(ahoCorasick: StringAhoCorasick) {
            this.wrappedAhoCorasick = ahoCorasick
            this.addString = ahoCorasick::add
        }

        constructor(ahoCorasick: AhoCorasick<String>) {
            this.wrappedAhoCorasick = ahoCorasick
            this.addString = { string -> ahoCorasick.add(string, string) }
        }

        fun build() = wrappedAhoCorasick.build()

        //fun parse(input: String): List<String> = wrappedAhoCorasick.parse(input).map { it.value }.toList()

        fun buildWith(vararg strings: String) = apply {
            strings.forEach(addString)
            wrappedAhoCorasick.build()
        }
    }
}
